namespace ScrumSprintMonitor.Samples.Widgets
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Diagnostics;
	using System.IO;
	using System.Windows;
	using System.Windows.Controls;
	using System.Windows.Media;
	using System.Windows.Media.Imaging;

	using ContinuousLinq.Reactive;

	using Extensibility;

	using Infrastructure;

	using Ninject.Core;
	using Ninject.Core.Parameters;

	using UI.Wpf.ViewModels;
	using UI.Wpf.Views.MonitorWindow;

	#endregion

	internal class WidgetServerService : ReactiveObject,
	                                     IDisposable
	{
		private readonly IAddInHost<WidgetServerAddIn> addInHost;
		private readonly IKernel kernel;
		private readonly FrameworkElement teamMemberBadge;
		private readonly List<string> teamMemberBadgePathList = new List<string>();
		private IReportableData reportableData;
		private BindingList<ITeamMember> teamMemberList;

		static WidgetServerService()
		{
			var dependsOn = Register<WidgetServerService>();

			dependsOn.Call(me => me.OnTeamMembersChanged()).OnChanged(me => me.ReportableData.CurrentSprint.TeamMembers);
		}

		public WidgetServerService(IReportableData reportableData, IKernel kernel)
		{
			this.kernel = kernel;
			this.addInHost = kernel.Get<IAddInHost<WidgetServerAddIn>>();
			this.teamMemberBadge = new Viewbox
			                       	{
			                       		Child = new TeamMemberBadge()
			                       	};

			//ReportableData = reportableData;
		}

		private IReportableData ReportableData
		{
			[DebuggerStepThrough]
			get { return this.reportableData; }
			set
			{
				if (value == ReportableData)
				{
					return;
				}

				this.reportableData = value;

				OnPropertyChanged("ReportableData");
			}
		}

		private void OnTeamMembersChanged()
		{
			if (this.teamMemberList != null)
			{
				this.teamMemberList.ListChanged -= OnTeamMembersListChanged;
				this.teamMemberList = null;

				DeleteTeamMemberBadges();
			}

			if (ReportableData != null && ReportableData.CurrentSprint != null)
			{
				this.teamMemberList = ReportableData.CurrentSprint.TeamMembers;
			}

			if (this.teamMemberList != null)
			{
				int index = 0;
				foreach (var teamMember in this.teamMemberList)
				{
					GenerateTeamMemberWidget(teamMember, index++);
				}
				this.teamMemberList.ListChanged += OnTeamMembersListChanged;
			}
		}

		private void DeleteTeamMemberBadges()
		{
			foreach (string memberBadgePath in this.teamMemberBadgePathList)
			{
				try
				{
					if (File.Exists(memberBadgePath))
					{
						File.Delete(memberBadgePath);
					}
				}
				catch (IOException)
				{
				}
			}

			this.teamMemberBadgePathList.Clear();
		}

		private void GenerateTeamMemberWidget(ITeamMember teamMember, int index)
		{
			if (teamMember.Information.Unassigned)
			{
				if (index >= 0)
				{
					this.teamMemberBadgePathList.Insert(index, null);
				}
				return;
			}

			var parameters = new Dictionary<string, object>
			                 	{
			                 		{"model", teamMember},
			                 		{"currentSprint", ReportableData.CurrentSprint}
			                 	};
			var viewModel = this.kernel.Get<ITeamMemberViewModel>(With.Parameters.ConstructorArguments(parameters));

			this.teamMemberBadge.Width = 480;
			this.teamMemberBadge.Height = 480;
			this.teamMemberBadge.DataContext = viewModel;

			this.teamMemberBadge.Measure(new Size(this.teamMemberBadge.Width, this.teamMemberBadge.Height));
			this.teamMemberBadge.Arrange(new Rect(new Size(this.teamMemberBadge.Width, this.teamMemberBadge.Height)));

			this.teamMemberBadge.UpdateLayout();

			SaveVisualToFile(teamMember, index);
		}

		private void SaveVisualToFile(ITeamMember teamMember, int index)
		{
			var teamMemberBadgeBmp = new RenderTargetBitmap((int) this.teamMemberBadge.ActualWidth, (int) this.teamMemberBadge.ActualHeight, 96, 96,
			                                                PixelFormats.Pbgra32);
			teamMemberBadgeBmp.Render(this.teamMemberBadge);

			var png = new PngBitmapEncoder();
			string filePath = GetTeamMemberBadgePath(teamMember);
			if (File.Exists(filePath))
			{
				File.Delete(filePath);
			}

			png.Frames.Add(BitmapFrame.Create(teamMemberBadgeBmp));

			using (Stream stm = File.Create(filePath))
			{
				png.Save(stm);

				if (index >= 0)
				{
					this.teamMemberBadgePathList.Insert(index, filePath);
				}
			}

			//this.teamMemberBadge.DataContext = null;
		}

		private string GetTeamMemberBadgePath(ITeamMember teamMember)
		{
			return Path.Combine(this.addInHost.GetOutputDirectory(), string.Format("{0}.png", teamMember.Information.Name));
		}

		private void OnTeamMembersListChanged(object sender, ListChangedEventArgs e)
		{
			switch (e.ListChangedType)
			{
				case ListChangedType.Reset:
					break;
				case ListChangedType.ItemAdded:
				{
					var teamMember = this.teamMemberList[e.NewIndex];
					Dispatcher.BeginInvoke((Action) (() => GenerateTeamMemberWidget(teamMember, e.NewIndex)));
					break;
				}
				case ListChangedType.ItemDeleted:
				{
					Dispatcher.BeginInvoke((Action) (() =>
					                                 {
					                                 	string filePath = this.teamMemberBadgePathList[e.NewIndex];

					                                 	this.teamMemberBadgePathList.RemoveAt(e.NewIndex);
					                                 	if (File.Exists(filePath))
					                                 	{
					                                 		File.Delete(filePath);
					                                 	}
					                                 }));
					break;
				}
				case ListChangedType.ItemMoved:
					break;
				case ListChangedType.ItemChanged:
				{
					var teamMember = this.teamMemberList[e.NewIndex];
					Dispatcher.BeginInvoke((Action) (() => GenerateTeamMemberWidget(teamMember, -1)));
					break;
				}
				case ListChangedType.PropertyDescriptorAdded:
				case ListChangedType.PropertyDescriptorDeleted:
				case ListChangedType.PropertyDescriptorChanged:
					break;
				default:
					throw new ArgumentOutOfRangeException();
			}
		}

		#region Implementation of IDisposable

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		/// <filterpriority>2</filterpriority>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		private void Dispose(bool disposing)
		{
			if (disposing)
			{
				ReportableData = null;

				DeleteTeamMemberBadges();
			}
		}

		#endregion
	}
}